<!DOCTYPE html>
<html>
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="utf-8">
  
  <title>fabric链码API及相关开发实例 | 探花需拔根</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  
    <meta name="keywords" content="Bluarry,bluarry,Blog,探花,拔根,博客,区块链,算法,acm">
  
  <meta name="description" content="fabric链码开发中API的作用不言而喻。为了理解如何使用这些API，本文将从fabric源码给出的几个例子着手，意图讲清楚这些API的用法。">
<meta name="keywords" content="学习,linux,fabric">
<meta property="og:type" content="article">
<meta property="og:title" content="fabric链码API及相关开发实例">
<meta property="og:url" content="/2020/06/28/fabric链码API及相关开发实例/index.html">
<meta property="og:site_name" content="探花需拔根">
<meta property="og:description" content="fabric链码开发中API的作用不言而喻。为了理解如何使用这些API，本文将从fabric源码给出的几个例子着手，意图讲清楚这些API的用法。">
<meta property="og:locale" content="zh.yml">
<meta property="og:updated_time" content="2020-06-30T13:02:43.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="fabric链码API及相关开发实例">
<meta name="twitter:description" content="fabric链码开发中API的作用不言而喻。为了理解如何使用这些API，本文将从fabric源码给出的几个例子着手，意图讲清楚这些API的用法。">
  
  
    <link rel="icon" href="/favicon.ico">
  
  <link href="//cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
  <link rel="stylesheet" href="/css/style.css">
  <script src="/js/pace.min.js"></script>
  

  
  

  
<!-- Matomo -->
<script type="text/javascript">
	var _paq = window._paq || [];
	/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
	_paq.push(['trackPageView']);
	_paq.push(['enableLinkTracking']);
	(function() {
	  var u="//matomo.bluarry.top/";
	  _paq.push(['setTrackerUrl', u+'matomo.php']);
	  _paq.push(['setSiteId', '3']);
	  var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
	  g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
	})();
  </script>
  <!-- End Matomo Code -->

</head>
</html>
<body>
  <div id="container">
      <header id="header">
    <div id="banner"></div>
    <div id="header-outer">
        <div id="header-menu" class="header-menu-pos animated">
            <div class="header-menu-container">
                <a href="/" class="left">
                    <span class="site-title">探花需拔根</span>
                </a>
                <nav id="header-menu-nav" class="right">
                    
                    <a  href="/">
                        <i class="fa fa-home"></i>
                        <span>主页</span>
                    </a>
                    
                    <a  href="/archives">
                        <i class="fa fa-archive"></i>
                        <span>归档</span>
                    </a>
                    
                    <a  href="/friends">
                        <i class="fa fa-envira"></i>
                        <span>友链</span>
                    </a>
                    
                    <a  href="/about">
                        <i class="fa fa-user"></i>
                        <span>关于我</span>
                    </a>
                    
                </nav>
                <a class="mobile-header-menu-button">
                    <i class="fa fa-bars"></i>
                </a>
            </div>
        </div>
        <div id="header-row">
            <div id="logo">
                <a href="/">
                    <img src="/images/avatar/me-130x130.jpg" alt="logo">
                </a>
            </div>
            <div class="header-info">
                <div id="header-title">
                    
                    <h2>
                        清风觅影
                    </h2>
                    
                </div>
                <div id="header-description">
                    
                    <h3>
                        致需极,守静笃
                    </h3>
                    
                </div>
            </div>
            <nav class="header-nav">
                <div class="social">
                    
                        <a title="Home"  href="/">
                            <i class="fa fa-home fa-2x"></i></a>
                    
                        <a title="Github" target="_blank" href="//github.com/bluarry">
                            <i class="fa fa-github fa-2x"></i></a>
                    
                        <a title="mail" target="_blank" href="mailto://bluarry@qq.com">
                            <i class="fa fa-envelope-o fa-2x"></i></a>
                    
                </div>
            </nav>
        </div>
    </div>
</header>
      <div class="outer">
        <section id="main" class="body-wrap"><article id="post-fabric链码API及相关开发实例" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 class="post-title" itemprop="name">
      fabric链码API及相关开发实例
    </h1>
    <div class="post-title-bar">
      <ul>
          
              <li>
                  <i class="fa fa-book"></i>
                  
                      <a href="/categories/默认分类/">默认分类</a>
                  
                      <a href="/categories/学习分享/">学习分享</a>
                  
              </li>
          
        <li>
          <i class="fa fa-calendar"></i>  2020-06-28
        </li>
        <li>
          <i class="fa fa-eye"></i>
          <span id="busuanzi_value_page_pv"></span>
        </li>
      </ul>
    </div>
  

          
      </header>
    
    <div class="article-entry post-content" itemprop="articleBody">
      
            
            <blockquote>
<p>fabric链码开发中API的作用不言而喻。为了理解如何使用这些API，本文将从fabric源码给出的几个例子着手，意图讲清楚这些API的用法。</p>
</blockquote>
<a id="more"></a>
<h1 id="fabric链码API"><a href="#fabric链码API" class="headerlink" title="fabric链码API"></a>fabric链码API</h1><p>​    fabric的链码必须实现以下Chaincode 接口:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Chaincode <span class="keyword">interface</span> &#123;</span><br><span class="line">	Init(stub ChaincodeStubInterface) pb.Response </span><br><span class="line">  Invoke(stub ChaincodeStubInterface) pb.Response</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>​    可以看到，此接口的两个方法都以<code>ChaincodeStubInterface</code>为参数。事实上，stub为链码提供了丰富的API，功能包括对账本进行操作，读取交易参数，调用其他链码等。</p>
<p>​    实际上，shim.ChaincodeStubInterface提供了一系列API，可以分为四类： 账本状态交互API、交易信息相关API、参数读取API、其他API。</p>
<h2 id="账本状态交互API"><a href="#账本状态交互API" class="headerlink" title="账本状态交互API"></a>账本状态交互API</h2><p>​    众所周知，fabric记录的数据，我们称之为状态(state),这些纸以键值对(key-value)的形式存储。而账本状态交互API可以对账本状态进行操作，相应的，方法会更新交易提案的读写集。</p>
<div class="table-container">
<table>
<thead>
<tr>
<th style="text-align:center">API</th>
<th style="text-align:center">方法格式</th>
<th style="text-align:left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">GetState</td>
<td style="text-align:center">GetState(key string) ([]byte, error)</td>
<td style="text-align:left">负责查询账本，返回指定键对应的值</td>
</tr>
<tr>
<td style="text-align:center">PutState</td>
<td style="text-align:center">PutState(key string, value []byte) error</td>
<td style="text-align:left">尝试在账本中添加或更新一对键值。 这一对键值会被添 加到写集合中，等待 Committer进一步的验证，验证通 过后会真正写入到账本</td>
</tr>
<tr>
<td style="text-align:center">GetStateByRange</td>
<td style="text-align:center">GetStateByRange(startKey, endKey string) (StateQuerylteratorlnterface,error)</td>
<td style="text-align:left">查询指定范围内的键值， startKey、 endKey 分别 指定起始(包括)和终止(不包括)，当为空时默认是最大范围 。 返回结果是一个迭代器 StateQuerylteratorlnterface结构， 可以按照字典序迭代每个键值对, 最后需调用 Close()方 法关闭</td>
</tr>
<tr>
<td style="text-align:center">GetStateByPartialCompositeKey</td>
<td style="text-align:center">GetStateByPartialCompositeKey (objectType string, keys []string) (StateQuerylteratorlnterface, error)</td>
<td style="text-align:left">根据局部的复合键(前缀)返回所有匹配的键值。 返回结 果也是一个迭代器StateQuerylteratorlnterface结构，可以按照字典序迭代每个键值对，最后需调用 C lose()方法关闭</td>
</tr>
<tr>
<td style="text-align:center">GetHistoryForKey</td>
<td style="text-align:center">GetHistoryForKey(key string) (History Querylteratorlnterface, error)</td>
<td style="text-align:left">返回某个键的所有历史值 。 需要在节点配置中打开历史数据库特性 (Iedger.history.enableHistoryDatabase= true)</td>
</tr>
<tr>
<td style="text-align:center">GetQueryResult</td>
<td style="text-align:center">GetQueryResult(query string) (State Querylteratorlnterface, error)</td>
<td style="text-align:left">对(支持富查询功能的)状态数据库进行富查询( rich query)。 返回结果为迭代器结构 StateQueryIteratorInterface。 注意该方法不会被 Committer重新执行进行验证，因此， 不应该用于更新账本状态的交易中 。 目前仅有 CouchDB 类型的状态数据库支持富查询</td>
</tr>
</tbody>
</table>
</div>
<h2 id="交易信息相关API"><a href="#交易信息相关API" class="headerlink" title="交易信息相关API"></a>交易信息相关API</h2><p>此类API可以获取到与交易自身相关的数据。用户对链码的调用会产生一系列的交易提案，这些API支持查询当前交易提案的一些属性。</p>
<div class="table-container">
<table>
<thead>
<tr>
<th style="text-align:center">API</th>
<th style="text-align:center">方法格式</th>
<th style="text-align:left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">GetTxID</td>
<td style="text-align:center">GetTxID() string</td>
<td style="text-align:left">该方法返回交易提案中指定的交易 ID。一般情况下， 交易 ID 是在客户端生成提案时候产生的数字摘要，由 Nonce 随机串和签名者身份信息，一起进行 SHA256 哈希运行生成</td>
</tr>
<tr>
<td style="text-align:center">GetTxTimestamp</td>
<td style="text-align:center">GetTxTimestamp() (*timestamp. Timestamp, error)</td>
<td style="text-align:left">返回交易被创建时的客户端打上的时间戳 。 这个时间戳 是 直接从交易 ChannelHeader中提取的，所以在所有背书节点 (endorsers)处看到的值都相同</td>
</tr>
<tr>
<td style="text-align:center">GetBinding</td>
<td style="text-align:center">GetBinding() ([]byte， eπor)</td>
<td style="text-align:left">返回交易的 binding信息。<br> 注意:交易的 binding 信息是将交易提案的 nonse、 Creator、 epoch 等信息组合起来，再进行晗希得到的数字摘要</td>
</tr>
<tr>
<td style="text-align:center">GetSignecjProposal</td>
<td style="text-align:center">GetSignedProposal()(*pb.SignedProposal, eηor)</td>
<td style="text-align:left">返回该 stub的 SignedProposal结构，包括了跟交易提案相 关的所有数据</td>
</tr>
<tr>
<td style="text-align:center">GetCreator</td>
<td style="text-align:center">GetCreator() ([]byte, eπor)</td>
<td style="text-align:left">返回该交易的提交者的身份信息，从 signedProposal 中的 SignatureHeader.Creator 提取</td>
</tr>
<tr>
<td style="text-align:center">GetTransient</td>
<td style="text-align:center">GetTransient()(map[string][]byte, error)</td>
<td style="text-align:left">返回交易中带有的 一 些临时信息，从 ChaincodeProposalPayload.transient 域提取，可以存放一 些应用相关的保密信息，这些信息不会被写到账本中</td>
</tr>
</tbody>
</table>
</div>
<h2 id="参数读取API"><a href="#参数读取API" class="headerlink" title="参数读取API"></a>参数读取API</h2><p>调用链码事支持传入若干参数，参数可通过API读取。</p>
<div class="table-container">
<table>
<thead>
<tr>
<th style="text-align:center">API</th>
<th style="text-align:center">方法格式</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">GetArgs</td>
<td style="text-align:center">GetArgs() [][]byte</td>
<td>提取调用链码时交易 Proposal 中指定的参数，以字节串 (ByteArray)数组形式返回。 可以在Init或Invoke方法中使用。 这些参数从 ChaincodeSpec结构中的 Input域直接提取</td>
</tr>
<tr>
<td style="text-align:center">GetArgsSlice</td>
<td style="text-align:center">GetArgsSlice() ([]byte, error)</td>
<td>提取调用链码时交易 Proposal 中指定的参数，以字节串形式返回</td>
</tr>
<tr>
<td style="text-align:center">GetFunctionAndParameters</td>
<td style="text-align:center">GetFunctionAndParameters()(string, []string)</td>
<td>这是链码开发者和用户约定俗成的习惯，即在 Init/Invoke方法中编写实现若干子函数，用户调用时以第一个参数作为函数名 ， 链码中的代码根据函数名称可以仅执行对应的分支处理逻辑</td>
</tr>
<tr>
<td style="text-align:center">GetStringArgs</td>
<td style="text-align:center">GetStringArgs() []string</td>
<td>提取调用链码时交易 Proposal 中指定的参数，以 字 符串 (String) 数组形式返回</td>
</tr>
</tbody>
</table>
</div>
<h2 id="其他API"><a href="#其他API" class="headerlink" title="其他API"></a>其他API</h2><p>上面是常用的API，同时还有一些辅助API</p>
<div class="table-container">
<table>
<thead>
<tr>
<th style="text-align:center">API</th>
<th style="text-align:center">方法格式</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">CreateCompositeKey</td>
<td style="text-align:center">CreateCompositeKey(objectType string, attributes []string) (string, error)</td>
<td>给定一组属性( attributes)，该 API 将这些属性组合起来构造返回一个复合键 。 返回的复合键可以被 PutState 等方法使用 。 objectType 和 attributes 只允许合法的 utf8 字符串，并且不能包含 U+OOOO 和 U+10FFFF</td>
</tr>
<tr>
<td style="text-align:center">SplitCompositeKey</td>
<td style="text-align:center">SplitCompositeKey(compositeKey string) (string, []string, error)</td>
<td>该方法与 CreateCompositeKey方法相对，给定一个复合键，将其拆分为构造复合键时所用的属性</td>
</tr>
<tr>
<td style="text-align:center">InvokeChaincode</td>
<td style="text-align:center">InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response</td>
<td>调用另一个链码中的 Invoke方法，如果被调用链码在同一个通道内，则添加其读写集合信息到调用交易; 否则执行调用但不影响读写集合信息 。 如果 channel 为空 ，则默认为当前通道。 目前仅限于读操作，同时不会生成新的交易</td>
</tr>
<tr>
<td style="text-align:center">SetEvent</td>
<td style="text-align:center">SetEvent(name string, payload []byte) error</td>
<td>设定当这个交易在 Committer处被认证通过， 写入到 区块时发送的事件( event)</td>
</tr>
</tbody>
</table>
</div>
<h1 id="应用开发案例"><a href="#应用开发案例" class="headerlink" title="应用开发案例"></a>应用开发案例</h1><p>上述API并不能代表现在的所有API，但是是常用的API应该已经覆盖到了，所以接下来，将在案例中体验这些API</p>
<h2 id="案例一-转账"><a href="#案例一-转账" class="headerlink" title="案例一: 转账"></a>案例一: 转账</h2><p>fabric1.4版本中自带一些完整的链码，本节将以Go语言的典型链码chaincode_example02进行学习。</p>
<p>此链码代码位置在<code>examples/chaincode/go/examples02/chaincode.go</code></p>
<h3 id="1-链码结构"><a href="#1-链码结构" class="headerlink" title="1. 链码结构"></a>1. 链码结构</h3><p>链码的基本结构如下所示：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> example02</span><br><span class="line"></span><br><span class="line"><span class="comment">//导入必要的包</span></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">……</span><br><span class="line">)</span><br><span class="line"><span class="comment">//声明链码结构体</span></span><br><span class="line"><span class="keyword">type</span> SimpleChaincode <span class="keyword">struct</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//实现Init方法</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(t *SimpleChaincode)</span> <span class="title">Init</span><span class="params">(stub shim.ChaincodeStubInterface)</span> <span class="title">pb</span>.<span class="title">Response</span></span> &#123;</span><br><span class="line">  ……</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//实现Invoke方法</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(t *SimpleChaincode)</span> <span class="title">Invoke</span><span class="params">(stub shim.ChaincodeStubInterface)</span> <span class="title">pb</span>.<span class="title">Response</span></span> &#123;</span><br><span class="line">    ……</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>具体的实现中，Invoke方法又存在不同的分支，如下所示:</p>
<div class="table-container">
<table>
<thead>
<tr>
<th style="text-align:center">方法</th>
<th style="text-align:center">分支方法</th>
<th style="text-align:center">功能</th>
<th style="text-align:center">参数示例</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">Init</td>
<td style="text-align:center">无</td>
<td style="text-align:center">添加两个实体和初始余额</td>
<td style="text-align:center">[“init”,”a”,”100”,”b”,”200”]</td>
</tr>
<tr>
<td style="text-align:center"></td>
<td style="text-align:center">query</td>
<td style="text-align:center">查询一个实体的余额</td>
<td style="text-align:center">[“query”,”a”]</td>
</tr>
<tr>
<td style="text-align:center">Invoke</td>
<td style="text-align:center">Invoke</td>
<td style="text-align:center">一个实体向另一个实体转账</td>
<td style="text-align:center">[“invoke”,”a”,”b”,”50”]</td>
</tr>
<tr>
<td style="text-align:center"></td>
<td style="text-align:center">Delete</td>
<td style="text-align:center">删除一个实体</td>
<td style="text-align:center">[“delete”,”b”]</td>
</tr>
</tbody>
</table>
</div>
<h3 id="2-Init方法"><a href="#2-Init方法" class="headerlink" title="2. Init方法"></a>2. Init方法</h3><p>Init方法中，首先通过stub的GetFunctionAndPaeameters()方法提取本次调用的交易中所指定的参数：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">_, args := stub.GetFunctionAndParameters()</span><br></pre></td></tr></table></figure>
<p><code>GetFunctionAndParameters()</code>的返回值类型为:<code>function string, params []string</code>,其中第一个function为交易参数中的第一个参数，params为从第二个参数开始的所有参数。例如，当实例化链码时指定参数<code>{“Args”:[&quot;init&quot;,&quot;a&quot;,&quot;100&quot;,&quot;b&quot;,&quot;200&quot;]}</code>。由于我们的案例不需要第一个参数，所以用下划线忽略。</p>
<p>接下来检查args的参数的数量，其中值得说的是，<code>shim.Error()</code>,此函数将创建并返回一个状态为ERROR的Response消息。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(args) != <span class="number">4</span> &#123;</span><br><span class="line">	<span class="keyword">return</span> shim.Error(<span class="string">"Incorrect number of arguments. Expecting 4"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>接下来的代码：分别设置两个实体A,B，然后给相应的实体赋值基本余额数目。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Initialize the chaincode</span></span><br><span class="line">	A = args[<span class="number">0</span>]</span><br><span class="line">	Aval, err = strconv.Atoi(args[<span class="number">1</span>])</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> shim.Error(<span class="string">"Expecting integer value for asset holding"</span>)</span><br><span class="line">	&#125;</span><br><span class="line">	B = args[<span class="number">2</span>]</span><br><span class="line">	Bval, err = strconv.Atoi(args[<span class="number">3</span>])</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> shim.Error(<span class="string">"Expecting integer value for asset holding"</span>)</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>
<p>接下来，是<strong>最为关键</strong>的部分，将必要的状态值记录到分布式账本中。stub的<code>PutState()</code>函数将尝试在账本中添加或更新一堆键值。(实际上，这些值是发起了相应的交易，一直得等到区块链网络中，到最后的peer节点验证确认之后，才会写入账本。但是<strong>一般情况下</strong>，正常操作可以认为这一步会成功)</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Write the state to the ledger</span></span><br><span class="line">	err = stub.PutState(A, []<span class="keyword">byte</span>(strconv.Itoa(Aval)))</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> shim.Error(err.Error())</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	err = stub.PutState(B, []<span class="keyword">byte</span>(strconv.Itoa(Bval)))</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> shim.Error(err.Error())</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>
<p>到方法的最后，通过<code>shim.Success(nil)</code>创建并返回状态为OK的Response消息。</p>
<h3 id="3-Invoke方法"><a href="#3-Invoke方法" class="headerlink" title="3.Invoke方法"></a>3.Invoke方法</h3><p>该方法中，同样使用<code>GetFunctionAndParameters()</code>方法提取本次调用的交易中所指定的参数,然后根据function的值的不同，执行不同的分支处理逻辑。</p>
<p>此案例中，实现对三种分支逻辑的处理: <code>invoke</code>,<code>query</code>,<code>delete</code>,由代码可知:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> function ** <span class="string">"invoke"</span> &#123;</span><br><span class="line">		<span class="comment">// Make payment of X units from A to B</span></span><br><span class="line">		<span class="keyword">return</span> t.invoke(stub, args)</span><br><span class="line">	&#125; <span class="keyword">else</span> <span class="keyword">if</span> function ** <span class="string">"delete"</span> &#123;</span><br><span class="line">		<span class="comment">// Deletes an entity from its state</span></span><br><span class="line">		<span class="keyword">return</span> t.<span class="built_in">delete</span>(stub, args)</span><br><span class="line">	&#125; <span class="keyword">else</span> <span class="keyword">if</span> function ** <span class="string">"query"</span> &#123;</span><br><span class="line">		<span class="comment">// the old "Query" is now implemtned in invoke</span></span><br><span class="line">		<span class="keyword">return</span> t.query(stub, args)</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>
<p>方法又写了三个方法来对相应的代码逻辑进行处理。如果所有指定的方法不在这三种之中，将通过<code>shim.Error()</code>方法返回一个状态为ERROR的Response。</p>
<h4 id="1-query方法"><a href="#1-query方法" class="headerlink" title="1. query方法"></a>1. query方法</h4><p>query方法实现了查询一个实体的余额。</p>
<p>该方法需要传入一个参数，即实体的名称。如，参数指定为{“Args”:[“query”,”a”]}，表示查询实体a的余额。</p>
<p>通过代码可以看到，首先通过<code>GetState()</code>函数获取相应的实体的状态。通过查看源码知，此函数的原型如下，可以看到，函数接收键值，并返回相应的字节数组。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GetState(key <span class="keyword">string</span>) ([]<span class="keyword">byte</span>, error)</span><br></pre></td></tr></table></figure>
<p>query函数如果查询到余额，则返回<code>shim.Success(Avalbytes)</code>,其中<code>Avalbytes</code>为上述函数的返回值。返回的内容为状态为OK的Response消息，并将余额Avalbytes写入Response的Payload字段中。(其实也就是发起了一个交易，交易的字段中必须有Payload)。</p>
<h4 id="2-invoke方法"><a href="#2-invoke方法" class="headerlink" title="2. invoke方法"></a>2. invoke方法</h4><p>此方法主要实现两个实体之间进行转账。方法需要传入三个参数，分别为付款方，收款方，以及转账数额。例如: {“Args”:[“invoke”,”a”,”b”,”50”]}.</p>
<p>方法同样用到<code>GetState()</code>来获取双方的余额，然后进行转账操作，之后将新的状态使用<code>PutState()</code>写入账本。</p>
<h4 id="3-delete-方法"><a href="#3-delete-方法" class="headerlink" title="3.delete 方法"></a>3.delete 方法</h4><p>此方法类似于上面的两个方法，最为关键的是，调用了<code>DelState()</code>方法，该方法的原型如下:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(stub *ChaincodeStub)</span> <span class="title">DelState</span><span class="params">(key <span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;&#125;</span><br></pre></td></tr></table></figure>
<p>此方法将从账本中删去相应的记录，但是不要忘了这是在区块链里，该方法将会产生相应的交易，之后将写入区块链。</p>
<h2 id="案例二：-资产权属管理"><a href="#案例二：-资产权属管理" class="headerlink" title="案例二： 资产权属管理"></a>案例二： 资产权属管理</h2><p>利用区块链进行资产权进行管理是很普遍的应用场景。</p>
<p>本节将以大理石的权属管理为例，来理解如何在链码中定义一种资产，并围绕这种资产提供创建，查询，转移所有权等操作。</p>
<h3 id="1-链码结构-1"><a href="#1-链码结构-1" class="headerlink" title="1.链码结构"></a>1.链码结构</h3><p>链码的基本结构和案例一一样，故这里不做赘述。</p>
<p>此案例中，声明了一个struct如下:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> marble <span class="keyword">struct</span> &#123;</span><br><span class="line">	ObjectType <span class="keyword">string</span> <span class="string">`json:"docType"`</span> <span class="comment">//docType is used to distinguish the various types of objects in state database</span></span><br><span class="line">	Name       <span class="keyword">string</span> <span class="string">`json:"name"`</span>    <span class="comment">//the fieldtags are needed to keep case from bouncing around</span></span><br><span class="line">	Color      <span class="keyword">string</span> <span class="string">`json:"color"`</span></span><br><span class="line">	Size       <span class="keyword">int</span>    <span class="string">`json:"size"`</span></span><br><span class="line">	Owner      <span class="keyword">string</span> <span class="string">`json:"owner"`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>此结构相应的字段如其英文名称所述的意思。值得一提的是，<code>ObjectType</code>字段，用于将结构体序列化为特定格式(如，json)时，该字段的键的名称。</p>
<h3 id="2-Invoke方法"><a href="#2-Invoke方法" class="headerlink" title="2. Invoke方法"></a>2. Invoke方法</h3><p>由于此方法的分支方法较多，所以列表说明，如下：</p>
<div class="table-container">
<table>
<thead>
<tr>
<th>分支方法</th>
<th>功能</th>
<th>参数示例</th>
</tr>
</thead>
<tbody>
<tr>
<td>initMarble</td>
<td>创建一个大理石信息并写入账本</td>
<td>[“initMarble”,”marblel1”,”blue”,”35”,”tom”]</td>
</tr>
<tr>
<td>transferMarble</td>
<td>更改一个大理石的拥有者</td>
<td>[“transferMarble”,”marble2”,”jerry”]</td>
</tr>
<tr>
<td>transferMarblesBasedOnColor</td>
<td>更改指定颜色的所有大理石的拥有者</td>
<td>[“transferMarblesBasedOnColor”,”blue”,”jerry”]</td>
</tr>
<tr>
<td>delete</td>
<td>删除一个大理石的信息</td>
<td>[“delete”,”marblel1”]</td>
</tr>
<tr>
<td>readMarble</td>
<td>从账本中读取一个大理石信息</td>
<td>[“readMarble”,”marblel1”]</td>
</tr>
<tr>
<td>queryMarblesByOwner</td>
<td>返回指定拥有者拥有的所有大理 石的信息</td>
<td>[“queryMarblesByOwner”,”tom”]</td>
</tr>
<tr>
<td>queryMarbles</td>
<td>富查询( rich query)大理石 信息</td>
<td>[“queryMarbles”,”\\”selector\\”:{\\”owner\\”:\\”tom\\”}]</td>
</tr>
<tr>
<td>getHistoryForMarble</td>
<td>返回一个大理石的所有历史信息</td>
<td>[”getHistoryForMarble”,”marble1” ]</td>
</tr>
<tr>
<td>getMarblesByRange</td>
<td>返回所有名称在指定字典序范围内的大理石的信息</td>
<td>[”getMarblesByRange”,”marblel”,” marble3 ” ]</td>
</tr>
<tr>
<td>getMarblesByRangeWithPagination</td>
<td>分页之后，返回指定页的所有名称在指定字典序范围内的大理石的信息,最后一个参数为每次调用之后所返回的值</td>
<td>[“getMarblesByRangeWithPagination”,”marblel”,” marble3 ”,1,””]</td>
</tr>
<tr>
<td>queryMarblesWithPagination</td>
<td>富查询( rich query)大理石 信息,并分页返回相关信息</td>
<td>[“queryMarbles”,”\\”selector\\”:{\\”owner\\”:\\”tom\\”},”1”,””]</td>
</tr>
</tbody>
</table>
</div>
<p>由于方法数目比较多，所以接下来将对重要的函数进行介绍:</p>
<h4 id="1-initMarble方法"><a href="#1-initMarble方法" class="headerlink" title="1. initMarble方法"></a>1. initMarble方法</h4><p>方法将根据输入参数创建一个大理石，并写入账本。方法接收四个参数，如 案例二.2.表 所示。</p>
<p>方法首先读入参数，并判断相应参数的合法性，之后用这些参数进行查重。如果已经存在同样名称的大理石，则返回error的Response。</p>
<p>之后将创建marble类型的变量，并用json.Marshal()方法将自定义marble类型序列化为json对象。之后，将序列化之后的json字节数组写入账本。</p>
<p>之后用函数<code>CreateCompositeKey</code>创建复合key，因为并不需要再存一遍marble，所以，就赋值为0x00，然后存入账本。</p>
<p>创建复合key的代码如下：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">indexName := <span class="string">"color~name"</span></span><br><span class="line">	colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []<span class="keyword">string</span>&#123;marble.Color, marble.Name&#125;)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> shim.Error(err.Error())</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="comment">//  Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble.</span></span><br><span class="line">	<span class="comment">//  Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value</span></span><br><span class="line">	value := []<span class="keyword">byte</span>&#123;<span class="number">0x00</span>&#125;</span><br><span class="line">	stub.PutState(colorNameIndexKey, value)</span><br></pre></td></tr></table></figure>
<p>这里创建复合键的意义时将一部分属性也构造为索引的一部分，使得针对某些属性做查询的时，可以直接根据索引返回查询结果，而不需要提取完整的信息来作对比。</p>
<p>函数<code>CreateCompositeKey</code>的函数原型：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">createCompositeKey</span><span class="params">(objectType <span class="keyword">string</span>, attributes []<span class="keyword">string</span>)</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> &#123;&#125;</span><br></pre></td></tr></table></figure>
<p>此方法实际上会将objectType和attrbutes中的每个stirng串联起来，中间用<code>U+0000</code>分割，同时在开头加上<code>\x00</code>,标明该键为复合键。</p>
<h4 id="2-readMarble"><a href="#2-readMarble" class="headerlink" title="2. readMarble"></a>2. readMarble</h4><p>根据大理石的名称，从账本中查询并返回大理石的信息。方法接收一个参数，即大理石的名称。如果找到就返回，否则返回error的json字符串</p>
<h4 id="3-delete"><a href="#3-delete" class="headerlink" title="3. delete"></a>3. delete</h4><p>方法根据提供的大理石的名称，删除账本中的大理石信息。方法接收1个参数，即为大理石名称。在删除该键的同时，还需要删除所对应的颜色与名称的复合键。</p>
<h4 id="4-getMarblesByRange"><a href="#4-getMarblesByRange" class="headerlink" title="4. getMarblesByRange"></a>4. getMarblesByRange</h4><p>方法中调用了API <code>stub.GetStateByRange(startKey,endKey)</code>,该函数返回结果时一个迭代器<code>StateQueryIteratorInterface</code>结构。方法的原型如下：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(stub *ChaincodeStub)</span> <span class="title">GetStateByRange</span><span class="params">(startKey, endKey <span class="keyword">string</span>)</span> <span class="params">(StateQueryIteratorInterface, error)</span></span> &#123;&#125;</span><br></pre></td></tr></table></figure>
<p>对于迭代器<code>StateQueryIteratorInterface</code>的使用方法可参考如下函数：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">constructQueryResponseFromIterator</span><span class="params">(resultsIterator shim.StateQueryIteratorInterface)</span> <span class="params">(*bytes.Buffer, error)</span></span> &#123;</span><br><span class="line">	<span class="comment">// buffer is a JSON array containing QueryResults</span></span><br><span class="line">	<span class="keyword">var</span> buffer bytes.Buffer</span><br><span class="line">	buffer.WriteString(<span class="string">"["</span>)</span><br><span class="line"></span><br><span class="line">	bArrayMemberAlreadyWritten := <span class="literal">false</span></span><br><span class="line">	<span class="keyword">for</span> resultsIterator.HasNext() &#123;</span><br><span class="line">		queryResponse, err := resultsIterator.Next()</span><br><span class="line">		<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">			<span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="comment">// Add a comma before array members, suppress it for the first array member</span></span><br><span class="line">		<span class="keyword">if</span> bArrayMemberAlreadyWritten ** <span class="literal">true</span> &#123;</span><br><span class="line">			buffer.WriteString(<span class="string">","</span>)</span><br><span class="line">		&#125;</span><br><span class="line">		buffer.WriteString(<span class="string">"&#123;\"Key\":"</span>)</span><br><span class="line">		buffer.WriteString(<span class="string">"\""</span>)</span><br><span class="line">		buffer.WriteString(queryResponse.Key)</span><br><span class="line">		buffer.WriteString(<span class="string">"\""</span>)</span><br><span class="line"></span><br><span class="line">		buffer.WriteString(<span class="string">", \"Record\":"</span>)</span><br><span class="line">		<span class="comment">// Record is a JSON object, so we write as-is</span></span><br><span class="line">		buffer.WriteString(<span class="keyword">string</span>(queryResponse.Value))</span><br><span class="line">		buffer.WriteString(<span class="string">"&#125;"</span>)</span><br><span class="line">		bArrayMemberAlreadyWritten = <span class="literal">true</span></span><br><span class="line">	&#125;</span><br><span class="line">	buffer.WriteString(<span class="string">"]"</span>)</span><br><span class="line"></span><br><span class="line">	<span class="keyword">return</span> &amp;buffer, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="5-transferMarblesBasedOnColor"><a href="#5-transferMarblesBasedOnColor" class="headerlink" title="5. transferMarblesBasedOnColor"></a>5. transferMarblesBasedOnColor</h4><p>该方法将指定颜色的大理石的所有权转移到新的用户名下。</p>
<p>此方法主要用到了复合键的查询。<code>stub.GetStateByPartialCompositeKey</code>方法可以返回所有满足条件的键值对。其结果也是一个迭代器<code>StateQueryInteratorInterface</code>结构. 事实上，这里指定了前缀为当时设定的<code>objectType(&quot;color~name&quot;)</code>加上<code>attributes</code>的第一个string(color的值)。实际上，<code>GetStateByPartialCompositeKey</code>在实现上是以复合键前缀为起始，前缀加<code>utf8.MaxRune</code>为终止，通过调用范围查找返回所有匹配的结果。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Query the color~name index by color</span></span><br><span class="line">	<span class="comment">// This will execute a key range query on all keys starting with 'color'</span></span><br><span class="line">	coloredMarbleResultsIterator, err := stub.GetStateByPartialCompositeKey(<span class="string">"color~name"</span>, []<span class="keyword">string</span>&#123;color&#125;)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> shim.Error(err.Error())</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">defer</span> coloredMarbleResultsIterator.Close()</span><br><span class="line"><span class="comment">// Iterate through result set and for each marble found, transfer to newOwner</span></span><br><span class="line">	<span class="keyword">var</span> i <span class="keyword">int</span></span><br><span class="line">	<span class="keyword">for</span> i = <span class="number">0</span>; coloredMarbleResultsIterator.HasNext(); i++ &#123;</span><br><span class="line">		<span class="comment">// Note that we don't get the value (2nd return variable), we'll just get the marble name from the composite key</span></span><br><span class="line">		responseRange, err := coloredMarbleResultsIterator.Next()</span><br><span class="line">		<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">			<span class="keyword">return</span> shim.Error(err.Error())</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="comment">// get the color and name from color~name composite key</span></span><br><span class="line">		objectType, compositeKeyParts, err := stub.SplitCompositeKey(responseRange.Key)</span><br><span class="line">		<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">			<span class="keyword">return</span> shim.Error(err.Error())</span><br><span class="line">		&#125;</span><br><span class="line">		returnedColor := compositeKeyParts[<span class="number">0</span>]</span><br><span class="line">		returnedMarbleName := compositeKeyParts[<span class="number">1</span>]</span><br><span class="line">		fmt.Printf(<span class="string">"- found a marble from index:%s color:%s name:%s\n"</span>, objectType, returnedColor, returnedMarbleName)</span><br><span class="line"></span><br><span class="line">		<span class="comment">// Now call the transfer function for the found marble.</span></span><br><span class="line">		<span class="comment">// Re-use the same function that is used to transfer individual marbles</span></span><br><span class="line">		response := t.transferMarble(stub, []<span class="keyword">string</span>&#123;returnedMarbleName, newOwner&#125;)</span><br><span class="line">		<span class="comment">// if the transfer failed break out of loop and return error</span></span><br><span class="line">		<span class="keyword">if</span> response.Status != shim.OK &#123;</span><br><span class="line">			<span class="keyword">return</span> shim.Error(<span class="string">"Transfer failed: "</span> + response.Message)</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>
<p>值得一提的是，上面的程序段里，使用<code>stub.SplitCompositeKey()</code>方法拆分了复合键，得到构造复合键时所用的各个attributes，即大理石颜色和名称。得到名称后，通过内部调用<code>transferMarble()</code>方法更新拥有者。</p>
<h4 id="6-queryMarbles方法"><a href="#6-queryMarbles方法" class="headerlink" title="6. queryMarbles方法"></a>6. queryMarbles方法</h4><p>该方法通过使用支持副查询的数据库(如CouchDB)作为状态数据库，则可以进行规则更为复杂的富查询。</p>
<p>方法里使用了<code>stub.GetQueryResult</code>,传入的参数为富查询指令字符串。</p>
<p><strong>此方法所产生的交易，不会在最后阶段被校验，故而，这里最好只进行查询。</strong></p>
<h2 id="案例三-调用其他链码"><a href="#案例三-调用其他链码" class="headerlink" title="案例三: 调用其他链码"></a>案例三: 调用其他链码</h2><p>在同一个区块链上可以部署多个链码，多个链码之间可以互相调用。这种方式有助于将智能合约的工作模块化，并为应用开发带来更多的灵活性。</p>
<p>本节将通过链码<code>passthru</code>来进行学习链码相互调用。该链码的功能可以形容为同一个区块链中其他的“网关“，其对外暴露的Invoke接口功能可以使用户指定想要调用的其他的链码的ID，方法，参数。通过该”网关“链码传递给指定链码，获得调用结果后返回给用户。</p>
<p>该链码比较短，所以这里分析一下核心的Invoke方法，核心代码如下：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//helper</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p *PassthruChaincode)</span> <span class="title">iq</span><span class="params">(stub shim.ChaincodeStubInterface, function <span class="keyword">string</span>, args []<span class="keyword">string</span>)</span> <span class="title">pb</span>.<span class="title">Response</span></span> &#123;</span><br><span class="line">	<span class="keyword">if</span> function ** <span class="string">""</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> shim.Error(<span class="string">"Chaincode ID not provided"</span>)</span><br><span class="line">	&#125;</span><br><span class="line">	chaincodeID := function</span><br><span class="line"></span><br><span class="line">	<span class="keyword">return</span> stub.InvokeChaincode(chaincodeID, toChaincodeArgs(args...), <span class="string">""</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Invoke passes through the invoke call</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p *PassthruChaincode)</span> <span class="title">Invoke</span><span class="params">(stub shim.ChaincodeStubInterface)</span> <span class="title">pb</span>.<span class="title">Response</span></span> &#123;</span><br><span class="line">	function, args := stub.GetFunctionAndParameters()</span><br><span class="line">	<span class="keyword">return</span> p.iq(stub, function, args)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>其中最核心的是，调用其他链码需要使用<code>stub.InvokeChaincode()</code>。该方法用于调用另一个链码中的Invoke方法。值得注意的是，该方法目前仅限于读操作，同时不会生成新的交易。如果第三个参数为空，则默认为空当前通道，否则为指定的通道。</p>
<h2 id="案例四：-发送事件"><a href="#案例四：-发送事件" class="headerlink" title="案例四： 发送事件"></a>案例四： 发送事件</h2><p>Fabric应用程序除了通过主动查询来获取当前已确认的状态，还可以通过订阅并监听事件(event)来获取交易执行信息，用于进行交易或者审计。</p>
<p>本节通过学习<code>examples/chaincode/go/eventsender/eventsender.go</code>，来学习使用方法。</p>
<p>发送事件需要使用<code>stub.SetEvent</code>方法。方法的格式为:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SetEvent(name <span class="keyword">string</span>, payload []<span class="keyword">byte</span>) error</span><br></pre></td></tr></table></figure>
<p>其中，name表示事件的名称，payload为事件内容。</p>
<p>通过该方法，可以设定当这个交易在Committer处被认证通过，写入到区块时所发送的事件。</p>
<p>示例链码的invoke分支被调用的时候，会将记录子啊账本中的递增序列和Invoke传入的参数串联起来作为事件内容，以eventsender为事件名称，调用stub.SetEvent方法。</p>
<p>应用开发着可以使用SDK中封装的方法监听链码发出的事件，然后在此基础上作出相应的逻辑处理。当然还可以使用fabric提供的block -listener工具来监听。</p>
<p>block-listener工具位于<code>examples/events/block-listener</code>，展示了如何利用事件客户端来从网络中获取事件信息。具体实现参考源码，这里不做赘述。</p>

            <div class="post-copyright">
    <div class="content">
        <p>最后更新： 2020年06月30日 21:02</p>
        <p>原始链接： <a class="post-url" href="/2020/06/28/fabric链码API及相关开发实例/" title="fabric链码API及相关开发实例">/2020/06/28/fabric链码API及相关开发实例/</a></p>
        <footer>
            <a href="">
                <img src="/images/avatar/me-130x130.jpg" alt="bluarry">
                bluarry
            </a>
        </footer>
    </div>
</div>

      
        
            
<div class="page-reward">
    <a id="rewardBtn" href="javascript:;">赏</a>
</div>

<div id="reward" class="post-modal reward-lay">
    <a class="close" href="javascript:;" id="reward-close">×</a>
    <span class="reward-title">
        <i class="icon icon-quote-left"></i>
        请我吃糖~
        <i class="icon icon-quote-right"></i>
    </span>
    <div class="reward-content">
        
        <div class="reward-code">
            <img id="rewardCode" src="/images/wechat_code.jpg" alt="打赏二维码">
        </div>
        <div class="reward-select">
            
            <label class="reward-select-item checked" data-id="wechat" data-wechat="/images/wechat_code.jpg">
                <img class="reward-select-item-wechat" src="/images/wechat.png" alt="微信">
            </label>
            
            
            <label class="reward-select-item" data-id="alipay" data-alipay="/images/alipay_code.jpg">
                <img class="reward-select-item-alipay" src="/images/alipay.png" alt="支付宝">
            </label>
            
        </div>
    </div>
</div>


        
    </div>
    <footer class="article-footer">
        
        
<div class="post-share">
    <a href="javascript:;" id="share-sub" class="post-share-fab">
        <i class="fa fa-share-alt"></i>
    </a>
    <div class="post-share-list" id="share-list">
        <ul class="share-icons">
          <li>
            <a class="weibo share-sns" target="_blank" href="http://service.weibo.com/share/share.php?url=/2020/06/28/fabric链码API及相关开发实例/&title=《fabric链码API及相关开发实例》 — 探花需拔根&pic=images/avatar/me-130x130.jpg" data-title="微博">
              <i class="fa fa-weibo"></i>
            </a>
          </li>
          <li>
            <a class="weixin share-sns" id="wxFab" href="javascript:;" data-title="微信">
              <i class="fa fa-weixin"></i>
            </a>
          </li>
          <li>
            <a class="qq share-sns" target="_blank" href="http://connect.qq.com/widget/shareqq/index.html?url=/2020/06/28/fabric链码API及相关开发实例/&title=《fabric链码API及相关开发实例》 — 探花需拔根&source=
fabric链码开发中API的作用不言而喻。为了理解如何使用这些API，本文将从fabric源码给出的几个例子着手，意图讲清楚这些API的用法。
" data-title="QQ">
              <i class="fa fa-qq"></i>
            </a>
          </li>
          <li>
            <a class="facebook share-sns" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=/2020/06/28/fabric链码API及相关开发实例/" data-title="Facebook">
              <i class="fa fa-facebook"></i>
            </a>
          </li>
          <li>
            <a class="twitter share-sns" target="_blank" href="https://twitter.com/intent/tweet?text=《fabric链码API及相关开发实例》 — 探花需拔根&url=/2020/06/28/fabric链码API及相关开发实例/&via=" data-title="Twitter">
              <i class="fa fa-twitter"></i>
            </a>
          </li>
          <li>
            <a class="google share-sns" target="_blank" href="https://plus.google.com/share?url=/2020/06/28/fabric链码API及相关开发实例/" data-title="Google+">
              <i class="fa fa-google-plus"></i>
            </a>
          </li>
        </ul>
     </div>
</div>
<div class="post-modal wx-share" id="wxShare">
    <a class="close" href="javascript:;" id="wxShare-close">×</a>
    <p>扫一扫，分享到微信</p>
    <img src="" alt="微信分享二维码">
</div>

<div class="mask"></div>

        
        <ul class="article-footer-menu">
            
            
  <li class="article-footer-tags">
    <i class="fa fa-tags"></i>
      
    <a href="/tags/学习/" class="color3">学习</a>
      
    <a href="/tags/linux/" class="color1">linux</a>
      
    <a href="/tags/fabric/" class="color2">fabric</a>
      
  </li>

        </ul>
        
        

    <aside class="post-toc-pos post-toc-top" id="post-toc">
        <nav class="post-toc-wrap">
            <ol class="post-toc"><li class="post-toc-item post-toc-level-1"><a class="post-toc-link" href="#fabric链码API"><span class="post-toc-text">fabric链码API</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#账本状态交互API"><span class="post-toc-text">账本状态交互API</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#交易信息相关API"><span class="post-toc-text">交易信息相关API</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#参数读取API"><span class="post-toc-text">参数读取API</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#其他API"><span class="post-toc-text">其他API</span></a></li></ol></li><li class="post-toc-item post-toc-level-1"><a class="post-toc-link" href="#应用开发案例"><span class="post-toc-text">应用开发案例</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#案例一-转账"><span class="post-toc-text">案例一: 转账</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#1-链码结构"><span class="post-toc-text">1. 链码结构</span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#2-Init方法"><span class="post-toc-text">2. Init方法</span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#3-Invoke方法"><span class="post-toc-text">3.Invoke方法</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-4"><a class="post-toc-link" href="#1-query方法"><span class="post-toc-text">1. query方法</span></a></li><li class="post-toc-item post-toc-level-4"><a class="post-toc-link" href="#2-invoke方法"><span class="post-toc-text">2. invoke方法</span></a></li><li class="post-toc-item post-toc-level-4"><a class="post-toc-link" href="#3-delete-方法"><span class="post-toc-text">3.delete 方法</span></a></li></ol></li></ol></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#案例二：-资产权属管理"><span class="post-toc-text">案例二： 资产权属管理</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#1-链码结构-1"><span class="post-toc-text">1.链码结构</span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#2-Invoke方法"><span class="post-toc-text">2. Invoke方法</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-4"><a class="post-toc-link" href="#1-initMarble方法"><span class="post-toc-text">1. initMarble方法</span></a></li><li class="post-toc-item post-toc-level-4"><a class="post-toc-link" href="#2-readMarble"><span class="post-toc-text">2. readMarble</span></a></li><li class="post-toc-item post-toc-level-4"><a class="post-toc-link" href="#3-delete"><span class="post-toc-text">3. delete</span></a></li><li class="post-toc-item post-toc-level-4"><a class="post-toc-link" href="#4-getMarblesByRange"><span class="post-toc-text">4. getMarblesByRange</span></a></li><li class="post-toc-item post-toc-level-4"><a class="post-toc-link" href="#5-transferMarblesBasedOnColor"><span class="post-toc-text">5. transferMarblesBasedOnColor</span></a></li><li class="post-toc-item post-toc-level-4"><a class="post-toc-link" href="#6-queryMarbles方法"><span class="post-toc-text">6. queryMarbles方法</span></a></li></ol></li></ol></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#案例三-调用其他链码"><span class="post-toc-text">案例三: 调用其他链码</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#案例四：-发送事件"><span class="post-toc-text">案例四： 发送事件</span></a></li></ol></li></ol>
        </nav>
    </aside>
    

<nav id="article-nav">
  
    <a href="/2020/06/30/20200630-编译原理复习/" id="article-nav-newer" class="article-nav-link-wrap">

      <span class="article-nav-title">
        <i class="fa fa-hand-o-left" aria-hidden="true"></i>
        
          一道编程题目引发的对编译原理的回顾
        
      </span>
    </a>
  
  
    <a href="/2020/05/18/2020-05-18-fabric根据构建脚本手动启动first-network/" id="article-nav-older" class="article-nav-link-wrap">
      <span class="article-nav-title">fabric根据构建脚本手动启动first-network</span>
      <i class="fa fa-hand-o-right" aria-hidden="true"></i>
    </a>
  
</nav>




    <!-- HTML页面布局 -->
    <div id="tab-list">
        <ul id="ul1">
            
            
            
            <li id="gittalk" style="width: 25%;">gittalk</li>
            
            
            <li id="valine" style="width: 25%;">valine</li>
            
        </ul>
         
        
          
        <div id="dv_gitment" class="hide">
            <div id="git_comments"></div>;
        </div>
        
        
        <div id="dv_vment" class="show">
            <div class="comments vcomment" id="vcomments"></div>
        </div>
        
    </div>

    <script src="/js/tablist.js"></script>
   

    
        
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css">
<script src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script>
<script src="https://cdn.bluarry.top/cdn/js/md5.min.js"></script>
<!--<div id="git_comments"></div>-->
<script>
    // var gitment = new Gitment({
    //     owner: 'bluarry',
    //     repo: 'blog_comment',
    //     oauth: {
    //         client_id: '5c95f5b820d109c8f5c1',
    //         client_secret: 'f1cfe5b08f9a55923ba26a9ec8855b631002ad18',
    //     },
    // })
    var hrefs=location.href;
    var links=hrefs.split('/');
    var cr="";
    for (var i=0;i<Math.min(7,links.length);i++) {
        cr+=links[i];
    }
    console.log(cr);

    var gitalk = new Gitalk({
        clientID: '5c95f5b820d109c8f5c1', //Client ID
        clientSecret: 'f1cfe5b08f9a55923ba26a9ec8855b631002ad18', //Client Secret
        repo: 'blog_comment',//仓库名称
        owner: 'bluarry',//仓库拥有者
        admin: ['bluarry'],
        id: md5(cr),      // Ensure uniqueness and length less than 50
        distractionFreeMode: false  // Facebook-like distraction free mode
    });
    gitalk.render('git_comments');
</script>


    
        <!-- Valine Comments -->
<!-- <div class="comments vcomment" id="vcomments"></div>-->
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="//unpkg.com/valine@latest/dist/Valine.min.js"></script>
<!-- Valine Comments script -->
<script>
    var GUEST_INFO = ['nick','mail','link'];
    var guest_info = 'nick,mail'.split(',').filter(function(item){
        return GUEST_INFO.indexOf(item) > -1
    });
    new Valine({
        el: '#vcomments',
        notify: 'true' == 'true',
        verify: 'false' == 'true',
        appId: "DRrkhLYTmB6QwGy6sI6vCH8C-gzGzoHsz",
        appKey: "M4A4lrKwbixQmmeO5lVdY5l2",
        avatar: "mm",
        placeholder: "少侠，留下你的评论吧",
        guest_info: guest_info.length == 0 ? GUEST_INFO : guest_info,
        pageSize: "10"
    })
</script>
<!-- Valine Comments end -->

    


    </footer>
  </div>
</article>


</section>
        
      </div>
      <footer id="footer">
  <div class="outer">
    <div id="footer-info" class="inner">
      
<p>
    <span id="busuanzi_container_site_uv" style='display:none'>
        <i class="fa fa-user"></i>&nbsp;&nbsp;<span id="busuanzi_value_site_uv"></span>
    </span>&nbsp;&nbsp;|&nbsp;&nbsp;
    <span id="busuanzi_container_site_pv" style='display:none'>
        <i class="fa fa-eye"></i> &nbsp;&nbsp;<span id="busuanzi_value_site_pv"></span>
    </span>
</p>


      <p>
      &copy; 2016-2021 &nbsp;&nbsp;<i style="color: red;" class="fa fa-heart"></i>&nbsp;&nbsp; bluarry
      备案号: <a style="color: #999;" href="http://www.beian.miit.gov.cn/" target="_blank">陕ICP备2020012959号</a>
    </p>

    </div>
  </div>
</footer>
    <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<script src="//cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script>
  var mihoConfig = {
      root: "",
      animate: true,
      isHome: false,
      share: true,
      reward: 1
  }
</script>
<div class="sidebar">
    <div id="sidebar-search" title="Search">
        <i class="fa fa-search"></i>
    </div>
    <div id="sidebar-category" title="Categories">
        <i class="fa fa-book"></i>
    </div>
    <div id="sidebar-tag" title="Tags">
        <i class="fa fa-tags"></i>
    </div>
    <div id="sidebar-top">
        <span class="sidebar-top-icon"><i class="fa fa-angle-up"></i></span>
    </div>
</div>
<div class="sidebar-menu-box" id="sidebar-menu-box">
    <div class="sidebar-menu-box-container">
        <div id="sidebar-menu-box-categories">
            <a class="category-link" href="/categories/linux/">linux</a><a class="category-link" href="/categories/linux-学习/">linux 学习</a><a class="category-link" href="/categories/linux-学习-acm-onlinejudge-hustoj/">linux 学习 acm onlinejudge hustoj</a><a class="category-link" href="/categories/学习分享/">学习分享</a><a class="category-link" href="/categories/数据结构/">数据结构</a><a class="category-link" href="/categories/经验分享/">经验分享</a><a class="category-link" href="/categories/经验分享/学习分享/">学习分享</a><a class="category-link" href="/categories/默认分类/">默认分类</a><a class="category-link" href="/categories/默认分类/复习/">复习</a><a class="category-link" href="/categories/默认分类/复习/学习分享/">学习分享</a><a class="category-link" href="/categories/默认分类/学习分享/">学习分享</a><a class="category-link" href="/categories/默认分类/生活杂谈/">生活杂谈</a><a class="category-link" href="/categories/默认分类/经验分享/">经验分享</a><a class="category-link" href="/categories/默认分类/经验分享/学习分享/">学习分享</a>
        </div>
        <div id="sidebar-menu-box-tags">
            <a href="/tags/Java/" style="font-size: 10px;">Java</a> <a href="/tags/acm/" style="font-size: 12.86px;">acm</a> <a href="/tags/android/" style="font-size: 11.43px;">android</a> <a href="/tags/c/" style="font-size: 14.29px;">c++</a> <a href="/tags/fabric/" style="font-size: 11.43px;">fabric</a> <a href="/tags/kali/" style="font-size: 10px;">kali</a> <a href="/tags/linux/" style="font-size: 15.71px;">linux</a> <a href="/tags/linux学习/" style="font-size: 10px;">linux学习</a> <a href="/tags/mac/" style="font-size: 10px;">mac</a> <a href="/tags/mfc/" style="font-size: 10px;">mfc</a> <a href="/tags/onlinejudge/" style="font-size: 10px;">onlinejudge</a> <a href="/tags/sqlite/" style="font-size: 10px;">sqlite</a> <a href="/tags/sqlite3/" style="font-size: 11.43px;">sqlite3</a> <a href="/tags/ss/" style="font-size: 10px;">ss</a> <a href="/tags/vs/" style="font-size: 11.43px;">vs</a> <a href="/tags/xposed/" style="font-size: 10px;">xposed</a> <a href="/tags/xposed模块/" style="font-size: 10px;">xposed模块</a> <a href="/tags/专业知识/" style="font-size: 10px;">专业知识</a> <a href="/tags/侧链/" style="font-size: 10px;">侧链</a> <a href="/tags/内网穿透/" style="font-size: 10px;">内网穿透</a> <a href="/tags/区块链/" style="font-size: 11.43px;">区块链</a> <a href="/tags/复习/" style="font-size: 10px;">复习</a> <a href="/tags/学习/" style="font-size: 18.57px;">学习</a> <a href="/tags/安卓/" style="font-size: 12.86px;">安卓</a> <a href="/tags/小技巧/" style="font-size: 17.14px;">小技巧</a> <a href="/tags/数据库/" style="font-size: 10px;">数据库</a> <a href="/tags/数据结构/" style="font-size: 10px;">数据结构</a> <a href="/tags/日常使用问题/" style="font-size: 10px;">日常使用问题</a> <a href="/tags/最小生成树/" style="font-size: 11.43px;">最小生成树</a> <a href="/tags/最短路径/" style="font-size: 10px;">最短路径</a> <a href="/tags/比特币/" style="font-size: 11.43px;">比特币</a> <a href="/tags/算法/" style="font-size: 12.86px;">算法</a> <a href="/tags/算法笔记/" style="font-size: 11.43px;">算法笔记</a> <a href="/tags/经验/" style="font-size: 20px;">经验</a> <a href="/tags/编译原理/" style="font-size: 10px;">编译原理</a> <a href="/tags/翻译/" style="font-size: 10px;">翻译</a> <a href="/tags/自然计算/" style="font-size: 10px;">自然计算</a>
        </div>
    </div>
    <a href="javascript:;" class="sidebar-menu-box-close">&times;</a>
</div>
<div class="mobile-header-menu-nav" id="mobile-header-menu-nav">
    <div class="mobile-header-menu-container">
        <span class="title">Menus</span>
        <ul class="mobile-header-menu-navbar">
            
            <li>
                <a  href="/">
                    <i class="fa fa-home"></i><span>主页</span>
                </a>
            </li>
            
            <li>
                <a  href="/archives">
                    <i class="fa fa-archive"></i><span>归档</span>
                </a>
            </li>
            
            <li>
                <a  href="/friends">
                    <i class="fa fa-envira"></i><span>友链</span>
                </a>
            </li>
            
            <li>
                <a  href="/about">
                    <i class="fa fa-user"></i><span>关于我</span>
                </a>
            </li>
            
        </ul>
    </div>
    <div class="mobile-header-tag-container">
        <span class="title">Tags</span>
        <div id="mobile-header-container-tags">
            <a href="/tags/Java/" style="font-size: 10px;">Java</a> <a href="/tags/acm/" style="font-size: 12.86px;">acm</a> <a href="/tags/android/" style="font-size: 11.43px;">android</a> <a href="/tags/c/" style="font-size: 14.29px;">c++</a> <a href="/tags/fabric/" style="font-size: 11.43px;">fabric</a> <a href="/tags/kali/" style="font-size: 10px;">kali</a> <a href="/tags/linux/" style="font-size: 15.71px;">linux</a> <a href="/tags/linux学习/" style="font-size: 10px;">linux学习</a> <a href="/tags/mac/" style="font-size: 10px;">mac</a> <a href="/tags/mfc/" style="font-size: 10px;">mfc</a> <a href="/tags/onlinejudge/" style="font-size: 10px;">onlinejudge</a> <a href="/tags/sqlite/" style="font-size: 10px;">sqlite</a> <a href="/tags/sqlite3/" style="font-size: 11.43px;">sqlite3</a> <a href="/tags/ss/" style="font-size: 10px;">ss</a> <a href="/tags/vs/" style="font-size: 11.43px;">vs</a> <a href="/tags/xposed/" style="font-size: 10px;">xposed</a> <a href="/tags/xposed模块/" style="font-size: 10px;">xposed模块</a> <a href="/tags/专业知识/" style="font-size: 10px;">专业知识</a> <a href="/tags/侧链/" style="font-size: 10px;">侧链</a> <a href="/tags/内网穿透/" style="font-size: 10px;">内网穿透</a> <a href="/tags/区块链/" style="font-size: 11.43px;">区块链</a> <a href="/tags/复习/" style="font-size: 10px;">复习</a> <a href="/tags/学习/" style="font-size: 18.57px;">学习</a> <a href="/tags/安卓/" style="font-size: 12.86px;">安卓</a> <a href="/tags/小技巧/" style="font-size: 17.14px;">小技巧</a> <a href="/tags/数据库/" style="font-size: 10px;">数据库</a> <a href="/tags/数据结构/" style="font-size: 10px;">数据结构</a> <a href="/tags/日常使用问题/" style="font-size: 10px;">日常使用问题</a> <a href="/tags/最小生成树/" style="font-size: 11.43px;">最小生成树</a> <a href="/tags/最短路径/" style="font-size: 10px;">最短路径</a> <a href="/tags/比特币/" style="font-size: 11.43px;">比特币</a> <a href="/tags/算法/" style="font-size: 12.86px;">算法</a> <a href="/tags/算法笔记/" style="font-size: 11.43px;">算法笔记</a> <a href="/tags/经验/" style="font-size: 20px;">经验</a> <a href="/tags/编译原理/" style="font-size: 10px;">编译原理</a> <a href="/tags/翻译/" style="font-size: 10px;">翻译</a> <a href="/tags/自然计算/" style="font-size: 10px;">自然计算</a>
        </div>
    </div>
</div>
<div class="search-wrap">
    <span class="search-close">&times;</span>
        <a href="javascript:;" class="header-icon waves-effect waves-circle waves-light" id="back">
            <i class="icon icon-lg icon-chevron-left"></i>
        </a>
        <input class="search-field" placeholder="Search..." id="keywords">
        <a id="search-submit" href="javascript:;">
            <i class="fa fa-search"></i>
        </a>
    <div class="search-container" id="search-container">
        <ul class="search-result" id="search-result">
        </ul>
    </div>
</div>

<div id="search-tpl">
    <li class="search-result-item">
        <a href="{url}" class="search-item-li">
            <span class="search-item-li-title" title="{title}">{title}</span>
        </a>
    </li>
</div>
<script src="/js/search.js"></script>
<script src="/js/main.js"></script>


  <script src="//cdn.bootcss.com/particles.js/2.0.0/particles.min.js"></script>
  <div id="particles"></div>
  <script src="/js/particles.js"></script>







  <link rel="stylesheet" href="//cdn.bootcss.com/animate.css/3.5.0/animate.min.css">
  <script src="//cdn.bootcss.com/scrollReveal.js/3.0.5/scrollreveal.js"></script>
  <script src="/js/animate.js"></script>


  <script src="/js/pop-img.js"></script>
  <script>
     $(".article-entry p img").popImg();
  </script>

  </div>
</body>
</html>